package com.dana.friend.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.constants.Constants;
import com.core.constants.ExamListType;
import com.core.enums.ResultCode;
import com.dana.friend.domain.exam.Exam;
import com.dana.friend.domain.exam.ExamQuestion;
import com.dana.friend.domain.exam.dto.QueryExamDTO;
import com.dana.friend.domain.exam.vo.ExamVO;
import com.dana.friend.domain.user.UserExam;
import com.dana.friend.mapper.exam.ExamMapper;
import com.dana.friend.mapper.exam.ExamQuestionMapper;
import com.dana.friend.mapper.user.UserExamMapper;
import com.dana.redis.service.RedisService;
import com.dana.security.exception.ServiceException;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import com.core.constants.CacheConstant;

import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Component
public class ExamCacheManager {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    //从缓存中获取竞赛数量
    public Long getListSize(Integer examListType,Long userId) {
        //获取u:e:l+userId  getExamListKey看是具体刷新哪个列表是未完成的还是历史还是用户报名的
        String examListKey = getExamListKey(examListType,userId);
        //返回缓存中是否有数据
        return redisService.getListSize(examListKey);
    }

    //从缓存中获取竞赛题目数量
    public Long getExamQuestionListSize(Long examId){
        String examQuestionListKey =getExamQuestionListKey(examId);
        return redisService.getListSize(examQuestionListKey);
    }


    //将用户-竞赛的关系存储到redis中
    public void addUserExamCache(Long userId,Long examId){
        redisService.leftPushForList(getUserListKey(userId),examId);
    }

    public List<ExamVO> getExamVOList(QueryExamDTO examQueryDTO,Long userId) {
        //分页返回
        int start = (examQueryDTO.getPageNum() - 1) * examQueryDTO.getPageSize();
        int end = start + examQueryDTO.getPageSize() - 1; //下标需要 -1
        String examListKey = getExamListKey(examQueryDTO.getType(), userId);
        List<Long> examIdList = redisService.getCacheListByRange(examListKey, start, end, Long.class);
        //assembleExamVOList就是将examIdList每一个竞赛集合id拼接上e:d,竞赛id并且转换为ExamVO
        List<ExamVO> examVOList = assembleExamVOList(examIdList);
        if (CollectionUtil.isEmpty(examVOList)) {
            //说明redis中数据可能有问题 从数据库中查数据并且重新刷新缓存
            examVOList = getExamListByDB(examQueryDTO); //从数据库中获取数据
            refreshCache(examQueryDTO.getType(),userId);
        }
        return examVOList;
    }

    /**
     * 获取当前用户报名了哪些竞赛
     * @param userId
     * @return
     */
    public List<Long> getAllUserExamList(Long userId) {
        List<Long> userExamIdList = redisService
                .getCacheListByRange(getUserListKey(userId), 0, -1, Long.class);
        if (CollectionUtil.isNotEmpty(userExamIdList)){
            return userExamIdList;
        }
        //如果为空,就去数据库查
        //select exam_id from tb_user_exam where user_id=#{userId}
        List<UserExam> userExamList = userExamMapper
                .selectList(new LambdaQueryWrapper<UserExam>().eq(UserExam::getUserId, userId));
        if(userExamList==null){
            return null;
        }
        refreshCache(ExamListType.USER_EXAM_LIST.getValue(), userId);
        return userExamList
                .stream()
                .map(UserExam::getExamId)
                .collect(Collectors.toList());
    }

    //刷新缓存逻辑
    public void refreshCache(Integer examListType,Long userId) {
        List<Exam> examList = new ArrayList<>();
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            //查询未完赛的竞赛列表,将结果返回到list集合
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                    .gt(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getCreateTime));
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            //查询历史竞赛,将结果返回到list集合
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                    .le(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getCreateTime));
        }else if (ExamListType.USER_EXAM_LIST.getValue().equals(examListType)){
            List<ExamVO> examVOList = userExamMapper.selectUserExamList(userId);
            examList  = BeanUtil.copyToList(examVOList, Exam.class);
        }

        //如果数据库为空,直接返回,
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }

        //使用Map批量插入竞赛基本信息
        Map<String, Exam> examMap = new HashMap<>();
        //使用list插入竞赛id,根据竞赛id去查询基本信息
        List<Long> examIdList = new ArrayList<>();
        for (Exam exam : examList) {
            examMap.put(getDetailKey(exam.getExamId()), exam);
            examIdList.add(exam.getExamId());
        }
        //批量插入
        redisService.multiSet(examMap);  //刷新详情缓存
        //可能之前redis会有和数据库重复的数据,删除后再缓存数据库到redis
        redisService.deleteObject(getExamListKey(examListType,userId));
        //将竞赛id缓存到redis中
        redisService.rightPushAll(getExamListKey(examListType,userId), examIdList);      //刷新列表缓存
    }

    public void refreshExamQuestion(Long examId){
        List<ExamQuestion> examQuestionList = examQuestionMapper
                .selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder)
        );
        if (CollectionUtil.isEmpty(examQuestionList)){
            return;
        }
        List<Long> examQuestionIdList = examQuestionList
                .stream()
                .map(ExamQuestion::getQuestionId)
                .toList();
        redisService.rightPushAll(getExamQuestionListKey(examId),examQuestionIdList);

        //由于使用竞赛列表不是很频繁,所以没必要一直存储,可以指定有效期
        long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), LocalDateTime.now().plusDays(1)
                .withHour(0).withMinute(0).withSecond(0).withNano(0));
        redisService.expire(getExamQuestionListKey(examId), seconds, TimeUnit.SECONDS);
    }

    //获取竞赛中的第一个题目
    public Long getFirstQuestion(Long examId) {
        return redisService.index(getExamQuestionListKey(examId),0L,Long.class);
    }

    //获取竞赛中的当前题目的前一个题目
    public Object preQuestion(Long questionId,Long examId) {
        //拿到当前题目的位置
        Long index = redisService.indexOf(getExamQuestionListKey(examId), questionId);
        if (index==0){
            throw new ServiceException(ResultCode.FAILED_FIRST_QUESTION);
        }
        return redisService.index(getExamQuestionListKey(examId), index - 1, Long.class);
    }
    //获取竞赛中的当前题目的后一个题目
    public Object nextQuestion(Long questionId,Long examId) {
        //拿到当前题目的位置
        Long index = redisService.indexOf(getExamQuestionListKey(examId), questionId);
        //最后一个数据的位置
        Long lastIndex = getExamQuestionListSize(examId) - 1;
        if (index == lastIndex) {
            throw new ServiceException(ResultCode.FAILED_LAST_QUESTION);
        }
        return redisService.index(getExamQuestionListKey(examId), index + 1, Long.class);
    }



    private List<ExamVO> getExamListByDB(QueryExamDTO examQueryDTO) {
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        return examMapper.selectExamList(examQueryDTO);
    }

    private List<ExamVO> assembleExamVOList(List<Long> examIdList) {
        if (CollectionUtil.isEmpty(examIdList)) {
            //说明redis当中没数据 从数据库中查数据并且重新刷新缓存
            return null;
        }
        //拼接redis当中key的方法 并且将拼接好的key存储到一个list中
        List<String> detailKeyList = new ArrayList<>();
        for (Long examId : examIdList) {
            detailKeyList.add(getDetailKey(examId));
        }
        List<ExamVO> examVOList = redisService.multiGet(detailKeyList, ExamVO.class);
        CollUtil.removeNull(examVOList);
        if (CollectionUtil.isEmpty(examVOList) || examVOList.size() != examIdList.size()) {
            //说明redis中数据有问题 从数据库中查数据并且重新刷新缓存
            return null;
        }
        return examVOList;
    }

    private String getExamListKey(Integer examListType,Long userId) {
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            return CacheConstant.EXAM_UNFINISHED_LIST;
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            return CacheConstant.EXAM_HISTORY_LIST;
        }else {
            return CacheConstant.USER_EXAM_LIST+userId;
        }

    }

    private String getDetailKey(Long examId) {
        return CacheConstant.EXAM_DETAIL + examId;
    }


    private String getUserListKey(Long userId){
        return CacheConstant.USER_EXAM_LIST+userId;
    }

    private String getExamQuestionListKey(Long examId){
        return CacheConstant.EXAM_QUESTION_LIST+examId;
    }



}